package org.mybatis.generator.my.gen;

import com.alibaba.fastjson2.JSON;
import com.google.common.base.CaseFormat;
import com.google.common.base.Strings;
import org.apache.commons.lang3.StringUtils;
import org.mybatis.generator.api.MyBatisGenerator;
import org.mybatis.generator.config.*;
import org.mybatis.generator.internal.DefaultCommentGenerator;
import org.mybatis.generator.internal.DefaultShellCallback;
import org.mybatis.generator.my.bean.Clazz;
import org.mybatis.generator.my.bean.TableData;
import org.mybatis.generator.my.config.IConfig;
import org.mybatis.generator.my.plugins.CollectModelDataPlugin;
import org.mybatis.generator.my.plugins.MybatisPluginComment;
import org.mybatis.generator.my.utils.GenUtil;

import java.io.File;
import java.io.FileWriter;
import java.text.SimpleDateFormat;
import java.util.*;

public class BaseGenerator {
    /**
     * java file path
     */
    private static final String JAVA_PATH = "/src/main/java";

    /**
     * resources file path
     */
    private static final String RESOURCES_PATH = "/src/main/resources";

    // @date
    private static final String NOW = new SimpleDateFormat("yyyy-MM-dd").format(new Date());

    private IConfig config;

    public BaseGenerator(IConfig config) {
        this.config = config;
    }

    /**
     * 通过数据表名称生成代码，Model 名称通过解析数据表名称获得，下划线转大驼峰的形式。 如输入表名称 "t_user_detail" 将生成
     * TUserDetail、TUserDetailMapper、ITUserDetailService ...
     *
     * @param tableName
     * @param schemeName
     * @param modelName
     */
    public void genCode(String tableName, String primaryKey, String schemeName, String modelName) {
        String strPrimaryKey;
        if (Strings.isNullOrEmpty(primaryKey)) {
            strPrimaryKey = "CODE";
        } else {
            strPrimaryKey = primaryKey;
        }
        genCodeByCustomModelName(tableName, strPrimaryKey, schemeName, modelName);
    }

    /**
     * 通过数据表名称，和自定义的 Model 名称生成代码 如输入表名称 "t_user_detail" 和自定义的 Model 名称 "User"
     * 将生成 User、UserMapper、IUserService ...
     *
     * @param tableName
     * @param schemeName
     * @param modelName
     */
    public void genCodeByCustomModelName(String tableName, String primaryKey, String schemeName, String modelName) {
        createBasePath();
        TableData tableData = new TableData(primaryKey, tableName, schemeName);
        genModelAndMapper(tableData, modelName);
        genController();
        genService(primaryKey);
        genView();
        genExMapper();
        System.out.println("Generate " + tableName + " success.");
    }

    public void genModelAndMapper(TableData tableData, String modelName) {
        Context context = new Context(ModelType.FLAT);
        context.setId(UUID.randomUUID().toString());
        context.setTargetRuntime("MyBatis3DynamicSQL"); // MyBatis3DynamicSQL, MyBatis3, MyBatis3Simple
        context.addProperty(PropertyRegistry.CONTEXT_BEGINNING_DELIMITER, "`");
        context.addProperty(PropertyRegistry.CONTEXT_ENDING_DELIMITER, "`");

        // 默认false，把JDBC DECIMAL 和 NUMERIC 类型解析为 Integer，为 true时把JDBC DECIMAL 和 NUMERIC 类型解析为java.math.BigDecimal
        JavaTypeResolverConfiguration typeResolverConfiguration = new JavaTypeResolverConfiguration();
        typeResolverConfiguration.addProperty("forceBigDecimals", "false");
        context.setJavaTypeResolverConfiguration(typeResolverConfiguration);

        // 设置注释生成器
        CommentGeneratorConfiguration commentGeneratorConfiguration = new CommentGeneratorConfiguration();
        commentGeneratorConfiguration.setConfigurationType(DefaultCommentGenerator.class.getName());
        // 是否去除自动生成的注释
        commentGeneratorConfiguration.addProperty("suppressAllComments", "true");
        commentGeneratorConfiguration.addProperty("addRemarkComments", "true");
        context.setCommentGeneratorConfiguration(commentGeneratorConfiguration);

        JDBCConnectionConfiguration connectionConfiguration = new JDBCConnectionConfiguration();
        connectionConfiguration.setConnectionURL(config.getConnectionUrl());
        connectionConfiguration.setUserId(config.getUserId());
        connectionConfiguration.setPassword(config.getPassword());
        connectionConfiguration.setDriverClass(config.getDriverClass());
        connectionConfiguration.addProperty("remarksReporting", "true");
        connectionConfiguration.addProperty("nullCatalogMeansCurrent", "true");
        context.setJdbcConnectionConfiguration(connectionConfiguration);

        // add plugins
        if (config.isSerializable()) {
            // add model add serializable interface plugin
            PluginConfiguration pluginConfiguration = new PluginConfiguration();
            pluginConfiguration.setConfigurationType("org.mybatis.generator.plugins.SerializablePlugin");
            context.addPluginConfiguration(pluginConfiguration);
        }

        // add generate model field comment plugin
        PluginConfiguration pluginConfiguration = null;
        pluginConfiguration = new PluginConfiguration();
        pluginConfiguration.setConfigurationType(MybatisPluginComment.class.getName());
        context.addPluginConfiguration(pluginConfiguration);
        // add collect model dataMap plugin
        pluginConfiguration = new PluginConfiguration();
        pluginConfiguration.setConfigurationType(CollectModelDataPlugin.class.getName());
        context.addPluginConfiguration(pluginConfiguration);
        // add collect model dataMap plugin
//        pluginConfiguration = new PluginConfiguration();
//        pluginConfiguration.setConfigurationType(RemoveAtGeneratedPlugin.class.getName());
//        context.addPluginConfiguration(pluginConfiguration);

        // model generator
        JavaModelGeneratorConfiguration javaModelGeneratorConfiguration = new JavaModelGeneratorConfiguration();
        javaModelGeneratorConfiguration.setTargetProject(config.getOutputPath() + JAVA_PATH);
        javaModelGeneratorConfiguration.setTargetPackage(config.getModelPackage());
        context.setJavaModelGeneratorConfiguration(javaModelGeneratorConfiguration);
        // xml generator
        SqlMapGeneratorConfiguration sqlMapGeneratorConfiguration = new SqlMapGeneratorConfiguration();
        sqlMapGeneratorConfiguration.setTargetProject(config.getOutputPath() + RESOURCES_PATH);
        sqlMapGeneratorConfiguration.setTargetPackage(config.getMapperPackage());
        context.setSqlMapGeneratorConfiguration(sqlMapGeneratorConfiguration);
        // mapper generator
        JavaClientGeneratorConfiguration javaClientGeneratorConfiguration = new JavaClientGeneratorConfiguration();
        javaClientGeneratorConfiguration.setTargetProject(config.getOutputPath() + JAVA_PATH);
        javaClientGeneratorConfiguration.setTargetPackage(config.getMapperPackage());
        javaClientGeneratorConfiguration.setConfigurationType("XMLMAPPER"); // ANNOTATEDMAPPER, XMLMAPPER, MIXEDMAPPER
        context.setJavaClientGeneratorConfiguration(javaClientGeneratorConfiguration);

        TableConfiguration tableConfiguration = new TableConfiguration(context);
        tableConfiguration.setTableName(tableData.getTableName());
        if (!Strings.isNullOrEmpty(tableData.getScheme())) {
            tableConfiguration.setSchema(tableData.getScheme());
        }
        if (!Strings.isNullOrEmpty(modelName)) {
            tableConfiguration.setDomainObjectName(modelName);
        }
        tableConfiguration.setGeneratedKey(new GeneratedKey(tableData.getPrimaryKey(), config.getDialect(), false, null));
        context.addTableConfiguration(tableConfiguration);

        List<String> warnings;
        MyBatisGenerator generator;
        try {
            Configuration configuration = new Configuration();
            configuration.addContext(context);
            configuration.validate();

            DefaultShellCallback callback = new DefaultShellCallback(true);
            warnings = new ArrayList<>();
            generator = new MyBatisGenerator(configuration, callback, warnings);
            generator.generate(null);
//            System.out.println(JSON.toJSONString(CollectModelDataPlugin.getClazz()));
//            System.out.println(JSON.toJSONString(warnings));
        } catch (Exception e) {
            throw new RuntimeException("generate model and mapper fail.", e);
        }

        if (generator.getGeneratedJavaFiles().isEmpty()) {
            throw new RuntimeException("generate model and mapper fail: " + warnings);
        }
//        if (Strings.isNullOrEmpty(modelName)) {
//            modelName = GenUtil.tableNameConvertUpperCamel(tableData.getTableName());
//        }
    }

    public void genService(String primaryKey) {
        try {
            Map<String, Object> dataMap = createFreemarkerDataMap();
            Clazz clazz = CollectModelDataPlugin.getClazz();
            dataMap.put("clazz", clazz);
            dataMap.put("getPkMethod", "get" + StringUtils.toRootUpperCase(primaryKey.toLowerCase()));

            StringBuilder sbServiceFile = new StringBuilder(config.getOutputPath());
            sbServiceFile.append(JAVA_PATH)
                    .append(GenUtil.packageConvertPath(config.getServicePackage()))
                    .append("I")
                    .append(clazz.getClassName())
                    .append("Service.java");
            GenUtil.executeFreemarker(config, dataMap, sbServiceFile.toString(), "service.ftl");

            StringBuilder sbServiceImplFile = new StringBuilder(config.getOutputPath());
            sbServiceImplFile.append(JAVA_PATH)
                    .append(GenUtil.packageConvertPath(config.getServiceImplPackage()))
                    .append(clazz.getClassName())
                    .append("ServiceImpl.java");
            GenUtil.executeFreemarker(config, dataMap, sbServiceImplFile.toString(), "service-impl.ftl");
        } catch (Exception e) {
            throw new RuntimeException("generate service fail.", e);
        }
    }

    public void genController() {
        try {
            Map<String, Object> dataMap = createFreemarkerDataMap();
            Clazz clazz = CollectModelDataPlugin.getClazz();
            dataMap.put("clazz", clazz);

            StringBuilder sb = new StringBuilder(config.getOutputPath());
            sb.append(JAVA_PATH)
                    .append(GenUtil.packageConvertPath(config.getControllerPackage()))
                    .append(clazz.getClassName())
                    .append("Controller.java");
            GenUtil.executeFreemarker(config, dataMap, sb.toString(), "controller.ftl");
        } catch (Exception e) {
            throw new RuntimeException("generate controller fail.", e);
        }
    }

    public void genView() {
        try {
            Map<String, Object> dataMap = createFreemarkerDataMap();
            Clazz clazz = CollectModelDataPlugin.getClazz();
            dataMap.put("clazz", clazz);

            StringBuilder sb = new StringBuilder(config.getOutputPath());
            sb.append(JAVA_PATH)
                    .append(GenUtil.packageConvertPath(config.getViewPackage()))
                    .append(clazz.getClassName())
                    .append("VO.java");
            GenUtil.executeFreemarker(config, dataMap, sb.toString(), "view.ftl");
        } catch (Exception e) {
            throw new RuntimeException("generate view fail.", e);
        }
    }

    public void genExMapper() {
        try {
            Map<String, Object> dataMap = createFreemarkerDataMap();
            Clazz clazz = CollectModelDataPlugin.getClazz();
            dataMap.put("clazz", clazz);

            StringBuilder sb = new StringBuilder(config.getOutputPath());
            sb.append(JAVA_PATH)
                    .append(GenUtil.packageConvertPath(config.getMapperPackage()))
                    .append(clazz.getClassName())
                    .append("ExMapper.java");
            GenUtil.executeFreemarker(config, dataMap, sb.toString(), "mapper-ex.ftl");
            sb = new StringBuilder(config.getOutputPath());
            sb.append(RESOURCES_PATH)
                    .append("/")
                    .append(config.getMapperPosition())
                    .append("/")
                    .append(clazz.getClassName())
                    .append("ExMapper.xml");
            GenUtil.executeFreemarker(config, dataMap, sb.toString(), "mapper.xml.ftl");
        } catch (Exception e) {
            throw new RuntimeException("generate view fail.", e);
        }
    }

    public void createBasePath() {
        File file = new File(config.getOutputPath() + JAVA_PATH);
        if (!file.exists()) {
            file.mkdirs();
        }
        file = new File(config.getOutputPath() + RESOURCES_PATH);
        if (!file.exists()) {
            file.mkdirs();
        }
    }

    public Map<String, Object> createFreemarkerDataMap() {
        Map<String, Object> dataMap = new HashMap<>();
        dataMap.put("date", NOW);
        dataMap.put("author", config.getAuthor());
        dataMap.put("basePackage", config.getBasePackage());
        dataMap.put("modelPackage", config.getModelPackage());
        dataMap.put("viewPackage", config.getViewPackage());
        dataMap.put("mapperPackage", config.getMapperPackage());
        dataMap.put("servicePackage", config.getServicePackage());
        dataMap.put("serviceImplPackage", config.getServiceImplPackage());
        dataMap.put("controllerPackage", config.getControllerPackage());
        dataMap.put("controllerSuffix", config.getControllerSuffix());
        dataMap.put("viewSuffix", config.getViewSuffix());
        dataMap.put("mapperSuffix", config.getMapperSuffix());
        return dataMap;
    }
}
